#include "hwInfo.h"
#include <iostream>
#include <fstream>
#include <utility>
#include <errno.h>
#include <cstring>

#define DEFAULT_MEM_DEV         "/dev/mem"

#define FLAG_NO_FILE_OFFSET     (1 << 0)
#define FLAG_STOP_AT_EOT        (1 << 1)

#define SUPPORTED_SMBIOS_VER    0x030700

int 
HwInfo::calculate()
{
    if (sizeof(UINT8) != 1 || sizeof(UINT16) != 2 || sizeof(UINT32) != 4 || '\0' != 0) {
        std::cerr << "compiler incompatibility" << std::endl;
        return EXIT;
    }

    /*
	 * First try reading from sysfs tables.  The entry point file could
	 * contain one of several types of entry points, so read enough for
	 * the largest one, then determine what type it contains.
	 */
    size_t size = 0x20;
    UINT8 *buffer = readFile(_smbiosEntryPoint, 0, size);
    std::unique_ptr<UINT8[]> uniqueBuf(buffer);
    if (buffer != nullptr) {
        std::cout << "Getting SMBIOS data from sysfs." << std::endl;
        if (size >= 24 && memcmp(uniqueBuf.get(), "_SM3_", 5) == 0) {
			// if (smbios3_decode(buf, size, SYS_TABLE_FILE, FLAG_NO_FILE_OFFSET))
			// 	found++;
		} else if (size >= 31 && memcmp(uniqueBuf.get(), "_SM_", 4) == 0) {
            if (SUCCEED == smbiosDecode(uniqueBuf, size, _dmiPath, FLAG_NO_FILE_OFFSET))
                return SUCCEED;
        } else if (size >= 15 && memcmp(uniqueBuf.get(), "_DMI_", 5) == 0) {
			// if (legacy_decode(buf, SYS_TABLE_FILE, FLAG_NO_FILE_OFFSET))
			// 	found++;
		}
    }

    return SUCCEED;
}

UINT8*    
HwInfo::readFile(const std::string& filename, off_t base, size_t &maxLen)
{
    std::ifstream input;
    input.open(filename, std::ios::binary);
    if (!input.is_open()) {
        std::cerr << "Failed to open the file: "<< filename <<". " << strerror(errno) << std::endl;
        // if (errno != ENOENT)
		// 	perror(filename);
        return nullptr;
    }

    // Check file size, don't allocate more than can be read.
    input.seekg(0, std::ios::end);
    std::streamsize size = input.tellg();
    input.seekg(base, std::ios::beg);
    if (base >= size) {
        std::cerr << "Can't read data beyond EOF!" << std::endl;
        input.close();
        return nullptr;
    }

    if (maxLen > (size_t)size - base)
        maxLen = size - base;
    
    // 创建一个数组存储文件内容
    UINT8 *buffer = new unsigned char[maxLen];
    // 读取文件内容到数组
    input.read((char*)buffer, size);

    // 关闭文件
    input.close();

    return buffer;
}

int             
HwInfo::smbiosDecode(std::unique_ptr<UINT8[]>& buf, size_t bufLen, const std::string& devmem, UINT32 flags)
{
    if (buf[0x05] > bufLen) {
        std::cerr << "Entry point length too large (" << (unsigned int)buf[0x05] << " bytes, expected " << 0x1FU << ")." << std::endl;
        return FAILED;
    }

    /*
	 * The size of this structure is 0x1F bytes, but we also accept value
	 * 0x1E due to a mistake in SMBIOS specification version 2.1.
	 */
	if (buf[0x05] < 0x1E
	 || !checkSum(buf.get(), buf[0x05])
	 || memcmp(buf.get() + 0x10, "_DMI_", 5) != 0
	 || !checkSum(buf.get() + 0x10, 0x0F))
        return FAILED;
    
    UINT16 ver = (buf[0x06] << 8) + buf[0x07];
    dmiFixupVersion(ver);
    std::cout << "SMBIOS " << (ver >> 8) << "." << (ver & 0xFF) << " present." << std::endl;

    /* Maximum length, may get trimmed */
    UINT32 len = (UINT32)WORD(buf.get() + 0x16);
	UINT16 num = WORD(buf.get() + 0x1C);
    UINT8 *table = dmiGetTable(DWORD(buf.get() + 0x18), len, num, ver << 8, devmem, flags);
    if (table == nullptr)
        return FAILED;
    //std::unique_ptr<UINT8[]> uniqueBuf(table);

    dmiTableDecode(table, len, num, ver, flags);

    delete[] table;

    return SUCCEED;
}

int 
HwInfo::checkSum(const UINT8 *buf, size_t len) const
{
	UINT8 sum = 0;
	size_t a;

	for (a = 0; a < len; a++)
		sum += buf[a];
	return (sum == 0);
}

void            
HwInfo::dmiFixupVersion(UINT16 &ver) const
{
    /* Some BIOS report weird SMBIOS version, fix that up */
	switch (ver) {
		case 0x021F:
		case 0x0221:
            std::cout << "SMBIOS version fixup (2." << (ver & 0xFF) << " -> 2.3)." << std::endl;
			ver = 0x0203;
			break;
		case 0x0233:
            std::cout << "SMBIOS version fixup (2.51 -> 2.6)." << std::endl;
			ver = 0x0206;
			break;
	}
}

/* Allocates a buffer for the table, must be freed by the caller */
UINT8 *
HwInfo::dmiGetTable(off_t base, UINT32& len, UINT16  num, UINT32 ver, const std::string &devmem, UINT32 flags)
{
    if (ver > SUPPORTED_SMBIOS_VER)	{
        std::cout << "SMBIOS implementations newer than version "
                  << (SUPPORTED_SMBIOS_VER >> 16) << "." 
                  << ((SUPPORTED_SMBIOS_VER >> 8) & 0xFF) << "." 
                  << (SUPPORTED_SMBIOS_VER & 0xFF) 
                  << " are not fully supported by this version of dmidecode." 
                  << std::endl;
    }

    if (num)
        std::cout << num << " structures occupying " << len << " bytes." << std::endl;
    std::cout << "Table at 0x" << std::hex << ((unsigned long long)base) << std::endl;
    
    UINT8 *buf = nullptr;
    if (flags & FLAG_NO_FILE_OFFSET) {
        /*
		 * When reading from sysfs, the file may be shorter than announced. 
         * For SMBIOS v3 this is expcted, as we
		 * only know the maximum table size, not the actual table size.
		 * For older implementations (and for SMBIOS v3 too), this
		 * would be the result of the kernel truncating the table on parse error.
		 */
        size_t size = len;
        buf = readFile(devmem, 0, size);
        if (num && size != len) {
            std::cerr << "Wrong DMI structures length: " << len << "bytes"
                      << "announced, only " << size << "bytes available." << std::endl;
        }
        len = size;
    } else {
        std::cout << "wanring: mem chunk" << std::endl;
    }
    if (buf == nullptr)
        std::cerr << "Failed to read table, sorry." << std::endl;
    return buf;
}

void 
HwInfo::dmiGetHeader(DmiHeader& h, UINT8 *data) const
{
	h.type = data[0];
	h.length = data[1];
	h.handle = WORD(data + 2);
	h.data = data;
}

/* Replace non-ASCII characters with dots */
void 
HwInfo::asciiFilter(char *bp, size_t len) const
{
	size_t i;

	for (i = 0; i < len; i++)
		if (bp[i] < 32 || bp[i] >= 127)
			bp[i] = '.';
}

char *
HwInfo::dmiString(const DmiHeader &dm, UINT8 s, int filter)
{
	char *bp = (char *)dm.data;

	bp += dm.length;
	while (s > 1 && *bp)
	{
		bp += strlen(bp);
		bp++;
		s--;
	}

	if (!*bp)
		return NULL;

	if (filter)
		asciiFilter(bp, strlen(bp));

	return bp;
}

const char *
HwInfo::dmiString(const DmiHeader& dm, UINT8 s)
{
	char *bp;

	if (s == 0)
		return "Not Specified";

	bp = dmiString(dm, s, 1);
	if (bp == NULL)
		return "bad_index";

	return bp;
}

/*
 * Remember the system vendor for later use. We only actually store the
 * value if we know how to decode at least one specific entry type for
 * that vendor.
 */
void 
HwInfo::dmiSetVendor(const char *v, const char *p)
{
	const struct { const char *str; enum DMI_VENDORS id; } vendor[] = {
		{ "Acer",			            VENDOR_ACER },
		{ "HP",				            VENDOR_HP },
		{ "Hewlett-Packard",            VENDOR_HP },
		{ "HPE",			            VENDOR_HPE },
		{ "Hewlett Packard Enterprise",	VENDOR_HPE },
		{ "IBM",			            VENDOR_IBM },
		{ "LENOVO",			            VENDOR_LENOVO },
	};
	unsigned int i;
	size_t len;

	/*
	 * Often DMI strings have trailing spaces. Ignore these
	 * when checking for known vendor names.
	 */
	len = v ? strlen(v) : 0;
	while (len && v[len - 1] == ' ')
		len--;

	for (i = 0; i < ARRAY_SIZE(vendor); i++) {
		if (strlen(vendor[i].str) == len && strncmp(v, vendor[i].str, len) == 0) {
			_dmiVendor = vendor[i].id;
			break;
		}
	}
	_dmiProduct = p;
}

int 
HwInfo::dmiGetCpuidType(const DmiHeader &h)
{
    const UINT8 *data = h.data;
	const UINT8 *p = data + 0x08;
    UINT16 type = (data[0x06] == 0xFE && h.length >= 0x2A) ? WORD(data + 0x28) : data[0x06];

    if (type == 0x05) {
        /* 80386 */
		return CPUID_80386;
	} else if (type == 0x06) {
        /* 80486 */
		UINT16 dx = WORD(p);
		/*
		 * Not all 80486 CPU support the CPUID instruction, we have to find
		 * whether the one we have here does or not. Note that this trick
		 * works only because we know that 80486 must be little-endian.
		 */
		if ((dx & 0x0F00) == 0x0400
		 && ((dx & 0x00F0) == 0x0040 || (dx & 0x00F0) >= 0x0070)
		 && ((dx & 0x000F) >= 0x0003))
			return CPUID_X86_INTEL;
		else
			return CPUID_80486;
	}
	else if ((type >= 0x100 && type <= 0x102) /* ARM */
	      || (type >= 0x118 && type <= 0x119)) /* ARM */ {
		/*
		 * The field's format depends on the processor's support of
		 * the SMCCC_ARCH_SOC_ID architectural call. Software can determine
		 * the support for SoC ID by examining the Processor Characteristics field
		 * for "Arm64 SoC ID" bit.
		 */
		if (h.length >= 0x28
		 && (WORD(data + 0x26) & (1 << 9)))
			return CPUID_ARM_SOC_ID;
		else
			return CPUID_ARM_LEGACY;
	}
	else if ((type >= 0x0B && type <= 0x15) /* Intel, Cyrix */
	      || (type >= 0x28 && type <= 0x2F) /* Intel */
	      || (type >= 0xA1 && type <= 0xB3) /* Intel */
	      || type == 0xB5 /* Intel */
	      || (type >= 0xB9 && type <= 0xC7) /* Intel */
	      || (type >= 0xCD && type <= 0xCF) /* Intel */
	      || (type >= 0xD2 && type <= 0xDB) /* VIA, Intel */
	      || (type >= 0xDD && type <= 0xE0)) /* Intel */
		return CPUID_X86_INTEL;
	else if ((type >= 0x18 && type <= 0x1D) /* AMD */
	      || type == 0x1F /* AMD */
	      || (type >= 0x38 && type <= 0x3F) /* AMD */
	      || (type >= 0x46 && type <= 0x4F) /* AMD */
	      || (type >= 0x66 && type <= 0x6B) /* AMD */
	      || (type >= 0x83 && type <= 0x8F) /* AMD */
	      || (type >= 0xB6 && type <= 0xB7) /* AMD */
	      || (type >= 0xE4 && type <= 0xEF)) /* AMD */
		return CPUID_X86_AMD;
	else if ((type >= 0x258 && type <= 0x262) /* Loongarch */
	      || (type >= 0x26C && type <= 0x271)) /* Loongarch */
		return CPUID_LOONGARCH;

	/* neither X86 nor ARM */
	return CPUID_NONE;
}

/* Returns 1 if the buffer contains only printable ASCII characters */
int 
HwInfo::isPrintable(const UINT8 *data, int len) const
{
	for (int i = 0; i < len; i++)
		if (data[i] < 32 || data[i] >= 127)
			return 0;

	return 1;
}

/*
 * Several boards have a bug where some type 34 structures have their
 * length incorrectly set to 0x10 instead of 0x0B. This causes the
 * first 5 characters of the device name to be trimmed. It's easy to
 * check and fix, so do it, but warn.
 */
void 
HwInfo::dmiFixupType34(DmiHeader &h)
{
	UINT8 *p = h.data;

	/* Make sure the hidden data is ASCII only */
	if (h.length == 0x10 && isPrintable(p + 0x0B, 0x10 - 0x0B)) {
        fprintf(stderr, "Invalid entry length (%u). Fixed up to %u.\n", 0x10, 0x0B);
        h.length = 0x0B;
    }
}

void            
HwInfo::dmiTableDecode(UINT8 *buf, UINT32 len, UINT16 num, UINT16 ver, UINT32 flags)
{
    UINT8* data = buf;
    int i = 0;

    /*
     * First: Save specific values needed to decode OEM types.
     *        4 is the length of an SMBIOS structure header.
     */
    while ((i < num || !num) && (data + 4) <= (buf + len)) {
        DmiHeader h;
        dmiGetHeader(h, data);
        /*
		 * If a short entry is found (less than 4 bytes), not only it
		 * is invalid, but we cannot reliably locate the next entry.
		 * Also stop at end-of-table marker if so instructed.
		 */
		if (h.length < 4 || (h.type == 127 && (flags & FLAG_STOP_AT_EOT)))
			break;
        ++i;

        /* Look for the next handle */
		UINT8 *next = data + h.length;
        while ((unsigned long)(next - buf + 1) < len && (next[0] != 0 || next[1] != 0))
            next++;
        next += 2;

        /* Make sure the whole structure fits in the table */
		if ((unsigned long)(next - buf) > len)
			break;
        
        // Set vendor.
        if (h.type == 1 && h.length >= 6)
            dmiSetVendor(dmiString(h, data[0x04], 0), dmiString(h, data[0x05], 0));
        
        /* Remember CPUID type for HPE type 199 */
		if (h.type == 4 && h.length >= 0x1A && _cpuidType == CPUID_NONE)
			_cpuidType = dmiGetCpuidType(h);
		data = next;
    }

    /* Second: Actually decode the data */
    i = 0;
	data = buf;
	while ((i < num || !num) && (data + 4) <= (buf + len)) {
        UINT8 *next;
		DmiHeader h;
		dmiGetHeader(h, data);

        /* If a short entry is found (less than 4 bytes), not only it is invalid, 
         * but we cannot reliably locate the next entry.
         */
        if (h.length < 4) {
            std::cout << "Invalid entry length" << (unsigned int)h.length << ". DMI table is broken! Stop." << std::endl;
			break;
		}
		i++;

        /* Stop decoding at end of table marker */
		// if (h.type == 127) {
        //     std::cout << "Stop decoding at end of table marker" << std::endl;
        //     break;
        // }

        printf("Handle 0x%04X, DMI type %d, %d bytes\n", h.handle, h.type, h.length);

        /* Look for the next handle */
        next = data + h.length;
        while ((unsigned long)(next - buf + 1) < len  && (next[0] != 0 || next[1] != 0))
            next++;
        next += 2;

        /* Make sure the whole structure fits in the table */
        if ((unsigned long)(next - buf) > len)	{
            std::cout << "<TRUNCATED>" << std::endl;
            data = next;
            break;
        }

        /* Fixup a common mistake */
		if (h.type == 34)
			dmiFixupType34(h);
        
        dmiDecode(h, ver);

        data = next;

		/* SMBIOS v3 requires stopping at this marker */
		if (h.type == 127 && (flags & FLAG_STOP_AT_EOT))
			break;
    }
}

void            
HwInfo::dmiDecode(const DmiHeader &h, UINT16 ver)
{
    const UINT8 *data = h.data;
    /*
	 * Note: DMI types 37 and 42 are untested
	 */
    switch (h.type) {
    case 0: /* 7.1 BIOS Information */
        if (h.length < 0x12) 
            break;
        _biosInfos.setGeneral(h.length, dmiString(h, data[0x04]),dmiString(h, data[0x05]),
            WORD(data + 0x06), dmiString(h, data[0x08]),data[0x09], QWORD(data + 0x0A));
        _biosInfos.setExtendedBiosROMSize(h.length < 0x1A ? 16 : WORD(data + 0x18));
		if (h.length >= 0x13)
			_biosInfos.setCharacteristicsExtensionBytes(WORD(data + 0x12));

		if (h.length >= 0x18) {
			_biosInfos.setBiosMajorAndMinorRelease(data[0x14], data[0x15]);
			_biosInfos.setEcfMajorAndMinorRelease(data[0x16], data[0x18]);
		}
		_biosInfos.print();
		break;
	case 1: /* 7.2 System Information */
		if (h.length < 0x08)
			break;
		_systemInfos.setSmbiosVersion(ver);
		_systemInfos.setGeneral(h.length, dmiString(h, data[0x04]), dmiString(h, data[0x05]),
								dmiString(h, data[0x06]), dmiString(h, data[0x07]));
		if (h.length >= 0x19) {
			_systemInfos.setUUID(data + 0x08);
			_systemInfos.setWakeupType(data[0x18]);
		}
		if (h.length >= 0x1B) {
			_systemInfos.setSkuNumber(dmiString(h, data[0x19]));
			_systemInfos.setFamily(dmiString(h, data[0x1A]));
		}
		_systemInfos.print();
		break;
	case 2: /* 7.3 Base Board Information */
		if (h.length < 0x08) 
			break;
		_baseBoardInfos.setGeneral(h.length, dmiString(h, 0x04), dmiString(h, 0x05),
								   dmiString(h, 0x06), dmiString(h, 0x07));
		if (h.length >= 0x09)
			_baseBoardInfos.setAssetTag(dmiString(h, 0x08));
		if (h.length >= 0x0A)
			_baseBoardInfos.setFeatureFlags(data[0x09]);
		if (h.length >= 0x0E) {
			_baseBoardInfos.setLocationInChassis(dmiString(h, data[0x0A]));
			_baseBoardInfos.setChassisHandle(WORD(data + 0x0B));
			_baseBoardInfos.setBoardType(data[0x0D]);
		}
		if (h.length >= 0x0F &&
			h.length >= (0x0F + data[0x0E] * sizeof(UINT16)))
			_baseBoardInfos.setCoHandles(data[0x0E], data + 0x0F);

		_baseBoardInfos.print();
		break;
	case 3: /* 7.4 System Enclosure or Chassis Information */
		if (h.length < 0x09)
			break;
		_chassisInfos.setGeneral(h.length, dmiString(h, data[0x04]), data[0x05], dmiString(h, data[0x06]),
			 					 dmiString(h, data[0x07]), dmiString(h, data[0x08]));
		if (h.length >= 0x0D) 
			_chassisInfos.set2_1(data[0x09], data[0x0A], data[0x0B], data[0x0C]);
		if (h.length >= 0x11)
			_chassisInfos.setoemDefined(DWORD(data + 0x0D));
		if (h.length >= 0x13) {
			_chassisInfos.setHeight(data[0x11]);
			_chassisInfos.setNumberOfPowerCords(data[0x12]);
		}
		if (h.length >= 0x15 && (h.length >= (0x15 + data[0x13] * data[0x14]))) 
			_chassisInfos.setContainedElement(data[0x13], data[0x14], data + 0x15);
		if (h.length >= (0x16 + data[0x13] * data[0x14]))
			_chassisInfos.setSkuNumber(dmiString(h, data[0x15 + data[0x13] * data[0x14]]));

		_chassisInfos.print();
		break;
	case 4: /* 7.5 Processor Information */
		if (h.length < 0x1A) 
			break;
		_processorInfo.setVersion(ver);
		_processorInfo.setGeneral(h.length, dmiString(h, data[0x04]),
								  data[0x05], data[0x06],
								  dmiString(h, data[0x07]),
								  QWORD(data + 0x08),
								  dmiString(h, data[0x10]),
								  data[0x11], WORD(data + 0x12),
								  WORD(data + 0x14), WORD(data + 0x16),
								  data[0x18], data[0x19]);
		if (h.length >= 0x20)
			_processorInfo.set2_1(WORD(data + 0x1A), WORD(data + 0x1C), WORD(data + 0x1E));
		if (h.length >= 0x23)
			_processorInfo.set2_3(dmiString(h, data[0x20]), dmiString(h, data[0x21]), dmiString(h, data[0x22]));
		if (h.length >= 0x28) 
			_processorInfo.set2_5(data[0x23], data[0x24], data[0x25], WORD(data + 0x26));
		if (h.length >= 0x2A)
			_processorInfo.setProcessorFamily2(WORD(data + 0x28));
		if (h.length >= 0x30)
			_processorInfo.set3_0(WORD(data + 0x2A), WORD(data + 0x2C), WORD(data + 0x2E));
		if (h.length >= 0x32)
			_processorInfo.setThreadEnabled(WORD(data + 0x30));
		if (h.length >= 0x33)
			_processorInfo.setSocketType(dmiString(h, data[0x32]));
		_processorInfo.print();
		break;
	default:
		break;
    }
	
}